﻿using System;
using System.Globalization;
using System.Linq;
using System.Management.Instrumentation;
using OpenTap.Plugins.Interfaces.Common;

namespace OpenTap.Plugins.Pm
{
    [Display("Keysight U2022XA PM driver", Group: "OpenTap.Plugins", Description: "Keyight U2022XA Power Meter driver")]
    public class PmKeysightU202X : PmBase
    {
        [Display("Automatic calibration state.","If true, the automatic calibration will be executed in every 10 minutes")]
        public bool AutoCalibrationState { get; set; }
        [Display("Execute auto-calibration after Zeroing.", "If true, the automatic calibration will be executed after auto-zeroing")]
        public bool ExecuteCalibrationInZeroCal { get; set; }

        public PmKeysightU202X()
        {
            Name = "KeyU2022XA";
        }

        /// <inheritdoc />
        public override void Open()
        {
            if (IsConnected)    // Keep this check, because of no exception occurred with duplicated opening.
                throw new InvalidOperationException("Pm is already connected!");
            base.Open();

            var tmp = IdnString;
            if ( !((tmp.Contains("Agilent Technologies,U2021XA")) ||
                   (tmp.Contains("Agilent Technologies,U2022XA"))) )
                throw new ApplicationException("Not supported instrument(" + tmp + "). Supported instruments are: Agilent Technologies,U2021XA and U2022XA");

            if (MaxFrequencyHz == 0) // 1. run ONLY: selftest, read limits 
            {
                Log.Debug("PM:SYSTem:VERSion: " + ScpiQuery("SYST:VERS?"));

                // Visa.SetVisaTimeout(15000); => Not implemented here, because of set is done in TAP: Settings/Bench/Instruments
                Log.Debug("PM:scpiInstrument IoTimeout = " + IoTimeout / 1000); 

                if (SelfTest())
                    throw new InstrumentationException("PM:Selftest FAIL");
              
                Reset();
                Opc(TimeOutOpcTypicSec);
                QueryErrWithExc(true, "Reset()");

                tmp = ScpiQuery("SERV:SENS:FREQ:MIN?");  // 50 MHz of U2022XA
                MinFrequencyHz = Convert.ToInt64(Decimal.Parse(tmp, NumberStyles.Float));
                Log.Debug("PM MINimum frequency: " + MinFrequencyHz);

                tmp = ScpiQuery("SERV:SENS:FREQ:MAX?");  // 40 GHz of U2022XA
                MaxFrequencyHz = Convert.ToInt64(Decimal.Parse(tmp, NumberStyles.Float));
                Log.Debug("PM MAXimum frequency: " + MaxFrequencyHz);
            }else{
                Reset();
                Opc(TimeOutOpcTypicSec);
                QueryErrWithExc(true, "Reset()");
            }

            // Keep commented code below lines within this file because of, future development & debugging will use it. 
            //  All commented code below tested & working
 
            // SetDataFormat(EDataFormat.Ascii);            // On reset, the format is set to ASCii.
            // Log.Debug("PM:Data format: " + ScpiQuery("FORM?"));

            // ScpiCommand("UNIT:POW DBM");                 // On reset, all CALCulate blocks are set to DBM.
            // Opc(TimeOutOpcTypicSec);
            // QueryErrWithExc(true, "PM:Measurement unit set to DBM(UNIT:POW DBM)");
            // Log.Debug("PM:Measurement unit: " + ScpiQuery("UNIT:POW?"));

            if (AutoCalibrationState)
                SetAutoCalibrationState(EState.On);         
            else
                SetAutoCalibrationState(EState.Off);        // On reset, auto-calibration is enabled.
            // Log.Debug("PM:auto-calibration state: " + ScpiQuery("CAL:AUTO?"));

            SetStepDetectionState(EState.Off);              // On reset, step detection is enabled.
            // Log.Debug("PM:step detection state: " + ScpiQuery("SENS:AVER:SDET?"));

            // ScpiCommand("SENS:DET:FUNC NORM");           // On reset, the mode is set to NORMal.
            // Opc(TimeOutOpcTypicSec);
            // QueryErrWithExc(true, "PM:Open Measurement mode set to NORMal(SENS:DET:FUNC NORM)");
            // Log.Debug("PM:Measurement mode: " + ScpiQuery("SENS:DET:FUNC?"));

            SetAverageOff();                                // On reset, averaging is turned ON.
            // Log.Debug("PM:Averaging state: " + ScpiQuery("AVER?"));

            SetMeasurementSpeed(EMeasurementSpeed.Double);  // On reset, the speed is set to NORMal.
            // Log.Debug("PM:Measurement speed: " + ScpiQuery("MRAT?"));

            // SetOffSetValue(EState.Off, 0);               // On reset, channel offsets are disabled.
            // Log.Debug("PM:Channel offset: " + ScpiQuery("CORR:GAIN2:STAT?"));

            // SetTrigSource(ETrigSource.Immediate);        // On reset, the trigger source is set to IMMediate.
            // Log.Debug("PM:Trigger source: " + ScpiQuery("TRIG:SOUR?"));
        }

        /// <summary>
        ///     This command auto-calibrates channel A ONCE.
        /// </summary>
        private void ExecuteAutoCalibration()
        {
            Log.Debug("PM:ExecuteAutoCalibration start");
            ScpiCommand("CAL:AUTO ONCE");
            Opc(TimeOutOpcTypicSec);

            Log.Debug("PM:ExecuteAutoCalibration done. Status(" + ScpiQuery("CAL:AUTO?") + ")");
            QueryErrWithExc(true, "ExecuteAutoCalibration: CAL:AUTO ONCE");
        }

        /// <inheritdoc />
        public override void ExecuteZeroCompensation()
        {
            Log.Debug("PM:ExecuteZeroCompensation start");
            ScpiCommand("CAL:ZERO:AUTO ONCE");
            Opc(TimeOutOpcTypicSec);
            Log.Debug("PM:ExecuteZeroCompensation done. Status(" + ScpiQuery("CAL:ZERO:AUTO?") + ")");
            QueryErrWithExc(true, "ExecuteZeroCompensation: CAL:ZERO:AUTO ONCE");

            if (ExecuteCalibrationInZeroCal)
                ExecuteAutoCalibration();
        }

        /// <inheritdoc />
        public override double MeasurePower(long frequencyKHz)
        {
            Log.Debug("PM:MeasurePower start");

            // Make settings
            ScpiCommand("SENS:FREQ " + frequencyKHz * 1000);
            ScpiCommand("INIT:CONT OFF");

            // Calculate timeout for measurement
            double timeOutMeasSec = TimeOutOpcTypicSec;         // Normal case timeout            
            string respons1 = ScpiQuery("SENS:AVER:STAT?");            
            string respons2 = ScpiQuery("SENS:DET:FUNC?");         
            
            if (respons2 != "NORM\n" || respons1 != "0\n")      // Average enabled
            {
                if (!(Math.Abs(MeasSpeed - 20.000) < 0.000001 
                   || Math.Abs(MeasSpeed - 40.000) < 0.000001 
                   || Math.Abs(MeasSpeed - 3500.0) < 0.000001))
                    throw new ArgumentException("PM:measurePower MeasSpeed(" 
                        + MeasSpeed + ") cannot be used. Use: 20, 40 or 3500");

                respons1 = ScpiQuery("AVER:COUN:AUTO?");       
                if (respons1 == "1\n")
                {                                               // Auto-average Enabled
                    respons2 = ScpiQuery("CONF?");
                    string[] apu = respons2.Split(',');
                    if (apu[1] != "+1")
                        timeOutMeasSec = 512 / MeasSpeed;
                    else timeOutMeasSec = 64 / MeasSpeed;
                }else{
                    respons2 = ScpiQuery("AVER:COUN?");         // MANual average
                    timeOutMeasSec = Convert.ToDouble(respons2);
                    timeOutMeasSec = timeOutMeasSec / MeasSpeed;
                }
                timeOutMeasSec = timeOutMeasSec + 5;    // Theory calculated time + extra addition
            }

            Opc(TimeOutOpcTypicSec);
            QueryErrWithExc(true, "MS:MeasurePower settings: " + frequencyKHz * 1000 + "Hz");

            // Start measuring
            ScpiCommand("INIT");    // Sets the PM to wait-for-trigger state. If IMM => start measuring immediately
            Opc(timeOutMeasSec);

            // Read result
            respons1 = ScpiQuery("FETC?");
            Log.Debug("PM:MeasurePower response(" + respons1 + ")");
            QueryErrWithExc(true, "MS:MeasurePower meas");

            // Result conversion
            respons1 = respons1.Substring(0,
                respons1.IndexOf("\n", StringComparison.InvariantCulture) == -1
                    ? respons1.Length
                    : respons1.IndexOf("\n", StringComparison.InvariantCulture) + 1);

            double power = Double.NaN;
            if (respons1.Split(',').Any(s => double.TryParse(s, NumberStyles.Float,
                    CultureInfo.InvariantCulture, out power)))
            {
                return power;
            }

            //conversion not successed
            throw new ApplicationException("PM:Measure power result conversion not succeeded");
        }

        /// <summary>
        ///     This command sets auto-calibration routine when enabled. 
        ///     This adjusts PM for a zero power reading with or without power supplied to PM.
        /// </summary>
        /// <param name="state">If enabled(On), the auto-cal is auto-calibration is updated every 10 minutes. Other choice: Off => User run cal.</param>
        private void SetAutoCalibrationState(EState state)
        {
            switch (state)
            {
                case EState.On:
                    ScpiCommand("CAL:ZERO:AUTO ON");
                    ScpiCommand("CAL:AUTO ON");
                    break;

                case EState.Off:
                    ScpiCommand("CAL:ZERO:AUTO OFF");
                    ScpiCommand("CAL:AUTO OFF");
                    break;

                default:
                    throw new ArgumentException("PM:SetAutoCalibrationState Invalid parameter value(" + state + ")", "state");
            }

            Opc(TimeOutOpcTypicSec);
            QueryErrWithExc(true, "PM:SetAutoCalibrationState(" + state + ")");
        }

        public override double MeasureBurstPower(long frequencyKHz)
        {
            throw new NotImplementedException();
        }
    }
}
